/**
 * @file oled.c
 * @author (you@domain.com)
 * @brief 进行了简单修改，适用于0.91寸128X32的OLED屏
 * @version 0.1
 * @date 2024-10-18
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#include "oled.h"
#include "i2c.h"
#include <math.h>
#include <stdlib.h>

#ifdef MY_USE_CMSIS_OS
#include "cmsis_os.h"
#endif

#include "stdio.h"

OLED& OLED::getInstance()
{
  return only;
}
OLED OLED::only;//初始化静态成员

OLED& oled = OLED::getInstance();




#define OLED_Lower_column_address 0x00 //lower column address 设置列地址低4位
#define OLED_Higer_column_address 0X10 //higher column address 设置列地址高4位
#define OLED_Page_address 0xB0 //page address 页地址
#define OLED_Duty 0x1F //duty = 1/32 显示行数0-31




// ========================== 底层通信函数 ==========================

void OLED::Send(uint8_t *data, uint8_t len)
{
  HAL_StatusTypeDef send_result;
  send_result = HAL_I2C_Master_Transmit(&OLED_IIC_HANDLE, OLED_ADDRESS, data, len, HAL_MAX_DELAY);
  if(send_result != HAL_OK)
    printf("oled iic transmit fail %d\n", send_result);
}


void OLED::SendCmd(uint8_t cmd)
{
  static uint8_t sendBuffer[2] = {0X00, 0X00};
  sendBuffer[1] = cmd;
  Send(sendBuffer, 2);
}

// ========================== OLED驱动函数 ==========================

void OLED::Init()
{
  //// ========================== 128X64初始化 ==========================
  // //经试验，RES应当弄个引脚控制
  // GPIO_InitTypeDef GPIO_InitStruct = {0};
  // __HAL_RCC_GPIOB_CLK_ENABLE();
  // /*Configure GPIO pin : PtPin */
  // GPIO_InitStruct.Pin = OLED_RESET_Pin;
  // GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  // GPIO_InitStruct.Pull = GPIO_PULLUP;
  // GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  // HAL_GPIO_Init(OLED_RESET_GPIO_Port, &GPIO_InitStruct);


  // HAL_GPIO_WritePin(OLED_RESET_GPIO_Port, OLED_RESET_Pin, GPIO_PIN_RESET);

  // HAL_Delay(200);//引脚拉低200ms
  
  // HAL_GPIO_WritePin(OLED_RESET_GPIO_Port, OLED_RESET_Pin, GPIO_PIN_SET);


  // // HAL_Delay(20); // 单片机启动比OLED上电快,需要延迟等待一下
  // OLED_IIC_INIT();


  // SendCmd(0xAE);//关闭显示屏

  // SendCmd(OLED_Lower_column_address); //set lower column address 设置起始行地址
  // SendCmd(OLED_Higer_column_address); /*set higher column address*/

  // SendCmd(0x40); /*set display start line address */

  // SendCmd(OLED_Page_address);//set page address 	设置页面起始地址为页面寻址模式，0-7

  // SendCmd(0x81);// contract control 设置对比度
  // SendCmd(0xFF);// 选择0xff对比度，选择范围0x00-0xff

  // SendCmd(0xA1);// set segment remap 左右反置关(段重映射)，A0H 设置GDDRAM的COL0映射到驱动器输出SEG0，A1H 设置COL127映射到SEG0

  // SendCmd(0xA6);// normal / reverse 正常显示(1亮0灭)

  // SendCmd(0xA8);//	multiplex ratio 设置多路传输比率，显示行数
  // SendCmd(OLED_Duty);// duty (显示多少行)

  // SendCmd(0xC8);//Com scan direction 上下反置关(行重映射),C8,从COM[N-1]扫描到COM0;C0,设置 从COM0扫描到COM[N-1]，N为复用率

  // SendCmd(0xD3);// set display offset 设置垂直显示偏移(向上)
  // SendCmd(0x00);// 偏移0行

  // SendCmd(0xD5);// set osc division 设置DCLK分频和OSC频率
  // SendCmd(0x80);// 频率最高

  // SendCmd(0xD8);//set area color mode off
  // SendCmd(0x05);

  // SendCmd(0xD9);// set pre-charge period 设置预充电的持续时间
  // SendCmd(0xF1);

  // SendCmd(0xDA);// set COM pins 设置COM引脚配置
  // SendCmd(0x12);// 序列COM配置

  // SendCmd(0xDB);//set vcomh 调整Vcomh调节器的输出
  // SendCmd(0x30);

  // SendCmd(0x8D);// set charge pump enable 启用电荷泵
  // SendCmd(0x14);// 启用电荷泵

  // SendCmd(0xAF);// 开OLED显示

  // ClearVideoMemory();
  // ShowFrame();


  // ========================== 128X32初始化 ==========================
  HAL_Delay(20); // 单片机启动比OLED上电快,需要延迟等待一下
  OLED_IIC_INIT();

  SendCmd(0xAE);//关闭显示屏

  SendCmd(OLED_Lower_column_address); //set lower column address 设置起始行地址
  SendCmd(OLED_Higer_column_address); /*set higher column address*/

  SendCmd(0x00); /*set display start line*/

  SendCmd(OLED_Page_address);//set page address 	设置页面起始地址为页面寻址模式，0-7

  SendCmd(0x81);// contract control 设置对比度
  SendCmd(0xff);// 选择0xff对比度，选择范围0x00-0xff

  SendCmd(0xa1);// set segment remap 左右反置关(段重映射)，A0H 设置GDDRAM的COL0映射到驱动器输出SEG0，A1H 设置COL127映射到SEG0

  SendCmd(0xa6);// normal / reverse 正常显示(1亮0灭)

  SendCmd(0xa8);//	multiplex ratio 设置多路传输比率，显示行数
  SendCmd(OLED_Duty);// duty (显示多少行)

  SendCmd(0xC8);//Com scan direction 上下反置关(行重映射),C8,从COM[N-1]扫描到COM0;C0,设置 从COM0扫描到COM[N-1]，N为复用率

  SendCmd(0xd3);// set display offset 设置垂直显示偏移(向上)
  SendCmd(0x00);// 偏移0行

  SendCmd(0xd5);// set osc division 设置DCLK分频和OSC频率
  SendCmd(0xf0);// 频率最高

  SendCmd(0xd9);// set pre-charge period 设置预充电的持续时间
  SendCmd(0x22);

  SendCmd(0xda);// set COM pins 设置COM引脚配置
  SendCmd(0x02);// 序列COM配置,禁用左右反置

  SendCmd(0xdb);//set vcomh 调整Vcomh调节器的输出
  SendCmd(0x49);

  SendCmd(0x8d);// set charge pump enable 启用电荷泵
  SendCmd(0x14);// 启用电荷泵

  SendCmd(0xaf);// 开OLED显示

  ClearVideoMemory();
  ShowFrame();  
}


void OLED::DisPlay_On()
{
  SendCmd(0x8D); // 电荷泵使能
  SendCmd(0x14); // 开启电荷泵
  SendCmd(0xAF); // 点亮屏幕
}


void OLED::DisPlay_Off()
{
  SendCmd(0x8D); // 电荷泵使能
  SendCmd(0x10); // 关闭电荷泵
  SendCmd(0xAE); // 关闭屏幕
}


void OLED::SetColorMode(OLED_ColorMode mode)
{
  if (mode == NORMAL)
  {
    SendCmd(0xA6); // 正常显示
  }
  if (mode == REVERSED)
  {
    SendCmd(0xA7); // 反色显示
  }
}

// ========================== 显示显存内容 ==========================

void OLED::ShowFrame()
{
  static uint8_t sendBuffer[OLED_COLUMN + 1];
  sendBuffer[0] = 0x40;
  for (uint8_t i = 0; i < OLED_PAGE; i++)
  {
    SendCmd(OLED_Page_address + i); // 设置页地址
    SendCmd(OLED_Lower_column_address);     // 设置列地址低4位
    SendCmd(OLED_Higer_column_address);     // 设置列地址高4位
    memcpy(sendBuffer + 1, GRAM[i], OLED_COLUMN);
    Send(sendBuffer, OLED_COLUMN + 1);
  }
}

// ========================== 显存操作函数 ==========================


void OLED::ClearVideoMemory()
{
  memset(GRAM, 0, sizeof(GRAM));
}





void OLED::SetPixel(uint8_t x, uint8_t y, OLED_ColorMode color)
{
  if (x >= OLED_COLUMN || y >= OLED_ROW)
    return;
  if (!color)
  {
    GRAM[y / 8][x] |= 1 << (y % 8);
  }
  else
  {
    GRAM[y / 8][x] &= ~(1 << (y % 8));
  }
}


void OLED::SetByte_Fine(uint8_t page, uint8_t column, uint8_t data, uint8_t start, uint8_t end, OLED_ColorMode color)
{
  static uint8_t temp;
  if (page >= OLED_PAGE || column >= OLED_COLUMN)
    return;
  if (color)
    data = ~data;

  temp = data | (0xff << (end + 1)) | (0xff >> (8 - start));
  GRAM[page][column] &= temp;
  temp = data & ~(0xff << (end + 1)) & ~(0xff >> (8 - start));
  GRAM[page][column] |= temp;
  // 使用OLED_SetPixel实现
  // for (uint8_t i = start; i <= end; i++) {
  //   SetPixel(column, page * 8 + i, !((data >> i) & 0x01));
  // }
}


void OLED::SetByte(uint8_t page, uint8_t column, uint8_t data, OLED_ColorMode color)
{
  if (page >= OLED_PAGE || column >= OLED_COLUMN)
    return;
  if (color)
    data = ~data;
  GRAM[page][column] = data;
}


void OLED::SetBits_Fine(uint8_t x, uint8_t y, uint8_t data, uint8_t len, OLED_ColorMode color)
{
  uint8_t page = y / 8;
  uint8_t bit = y % 8;
  if (bit + len > 8)
  {
    SetByte_Fine(page, x, data << bit, bit, 7, color);
    SetByte_Fine(page + 1, x, data >> (8 - bit), 0, len + bit - 1 - 8, color);
  }
  else
  {
    SetByte_Fine(page, x, data << bit, bit, bit + len - 1, color);
  }
  // 使用OLED_SetPixel实现
  // for (uint8_t i = 0; i < len; i++) {
  //   SetPixel(x, y + i, !((data >> i) & 0x01));
  // }
}


void OLED::SetBits(uint8_t x, uint8_t y, uint8_t data, OLED_ColorMode color)
{
  uint8_t page = y / 8;
  uint8_t bit = y % 8;
  SetByte_Fine(page, x, data << bit, bit, 7, color);
  if (bit)
  {
    SetByte_Fine(page + 1, x, data >> (8 - bit), 0, bit - 1, color);
  }
}


void OLED::SetBlock(uint8_t x, uint8_t y, const uint8_t *data, uint8_t w, uint8_t h, OLED_ColorMode color)
{
  uint8_t fullRow = h / 8; // 完整的行数
  uint8_t partBit = h % 8; // 不完整的字节中的有效位数
  for (uint8_t i = 0; i < w; i++)
  {
    for (uint8_t j = 0; j < fullRow; j++)
    {
      SetBits(x + i, y + j * 8, data[i + j * w], color);
    }
  }
  if (partBit)
  {
    uint16_t fullNum = w * fullRow; // 完整的字节数
    for (uint8_t i = 0; i < w; i++)
    {
      SetBits_Fine(x + i, y + (fullRow * 8), data[fullNum + i], partBit, color);
    }
  }
  // 使用OLED_SetPixel实现
  // for (uint8_t i = 0; i < w; i++) {
  //   for (uint8_t j = 0; j < h; j++) {
  //     for (uint8_t k = 0; k < 8; k++) {
  //       if (j * 8 + k >= h) break; // 防止越界(不完整的字节
  //       SetPixel(x + i, y + j * 8 + k, !((data[i + j * w] >> k) & 0x01));
  //     }
  //   }
  // }
}

// ========================== 图形绘制函数 ==========================

void OLED::DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, OLED_ColorMode color)
{
  static uint8_t temp = 0;
  if (x1 == x2)
  {
    if (y1 > y2)
    {
      temp = y1;
      y1 = y2;
      y2 = temp;
    }
    for (uint8_t y = y1; y <= y2; y++)
    {
      SetPixel(x1, y, color);
    }
  }
  else if (y1 == y2)
  {
    if (x1 > x2)
    {
      temp = x1;
      x1 = x2;
      x2 = temp;
    }
    for (uint8_t x = x1; x <= x2; x++)
    {
      SetPixel(x, y1, color);
    }
  }
  else
  {
    // Bresenham直线算法
    int16_t dx = x2 - x1;
    int16_t dy = y2 - y1;
    int16_t ux = ((dx > 0) << 1) - 1;
    int16_t uy = ((dy > 0) << 1) - 1;
    int16_t x = x1, y = y1, eps = 0;
    dx = abs(dx);
    dy = abs(dy);
    if (dx > dy)
    {
      for (x = x1; x != x2; x += ux)
      {
        SetPixel(x, y, color);
        eps += dy;
        if ((eps << 1) >= dx)
        {
          y += uy;
          eps -= dx;
        }
      }
    }
    else
    {
      for (y = y1; y != y2; y += uy)
      {
        SetPixel(x, y, color);
        eps += dx;
        if ((eps << 1) >= dy)
        {
          x += ux;
          eps -= dy;
        }
      }
    }
  }
}


void OLED::DrawRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, OLED_ColorMode color)
{
  DrawLine(x, y, x + w, y, color);
  DrawLine(x, y + h, x + w, y + h, color);
  DrawLine(x, y, x, y + h, color);
  DrawLine(x + w, y, x + w, y + h, color);
}


void OLED::DrawFilledRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, OLED_ColorMode color)
{
  for (uint8_t i = 0; i < h; i++)
  {
    DrawLine(x, y + i, x + w, y + i, color);
  }
}


void OLED::DrawTriangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, OLED_ColorMode color)
{
  DrawLine(x1, y1, x2, y2, color);
  DrawLine(x2, y2, x3, y3, color);
  DrawLine(x3, y3, x1, y1, color);
}


void OLED::DrawFilledTriangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, OLED_ColorMode color)
{
  uint8_t a = 0, b = 0, y = 0, last = 0;
  if (y1 > y2)
  {
    a = y2;
    b = y1;
  }
  else
  {
    a = y1;
    b = y2;
  }
  y = a;
  for (; y <= b; y++)
  {
    if (y <= y3)
    {
      DrawLine(x1 + (y - y1) * (x2 - x1) / (y2 - y1), y, x1 + (y - y1) * (x3 - x1) / (y3 - y1), y, color);
    }
    else
    {
      last = y - 1;
      break;
    }
  }
  for (; y <= b; y++)
  {
    DrawLine(x2 + (y - y2) * (x3 - x2) / (y3 - y2), y, x1 + (y - last) * (x3 - x1) / (y3 - last), y, color);
  }
}


void OLED::DrawCircle(uint8_t x, uint8_t y, uint8_t r, OLED_ColorMode color)
{
  int16_t a = 0, b = r, di = 3 - (r << 1);
  while (a <= b)
  {
    SetPixel(x - b, y - a, color);
    SetPixel(x + b, y - a, color);
    SetPixel(x - a, y + b, color);
    SetPixel(x - b, y - a, color);
    SetPixel(x - a, y - b, color);
    SetPixel(x + b, y + a, color);
    SetPixel(x + a, y - b, color);
    SetPixel(x + a, y + b, color);
    SetPixel(x - b, y + a, color);
    a++;
    if (di < 0)
    {
      di += 4 * a + 6;
    }
    else
    {
      di += 10 + 4 * (a - b);
      b--;
    }
    SetPixel(x + a, y + b, color);
  }
}


void OLED::DrawFilledCircle(uint8_t x, uint8_t y, uint8_t r, OLED_ColorMode color)
{
  int16_t a = 0, b = r, di = 3 - (r << 1);
  while (a <= b)
  {
    for (int16_t i = x - b; i <= x + b; i++)
    {
      SetPixel(i, y + a, color);
      SetPixel(i, y - a, color);
    }
    for (int16_t i = x - a; i <= x + a; i++)
    {
      SetPixel(i, y + b, color);
      SetPixel(i, y - b, color);
    }
    a++;
    if (di < 0)
    {
      di += 4 * a + 6;
    }
    else
    {
      di += 10 + 4 * (a - b);
      b--;
    }
  }
}


void OLED::DrawEllipse(uint8_t x, uint8_t y, uint8_t a, uint8_t b, OLED_ColorMode color)
{
  int xpos = 0, ypos = b;
  int a2 = a * a, b2 = b * b;
  int d = b2 + a2 * (0.25 - b);
  while (a2 * ypos > b2 * xpos)
  {
    SetPixel(x + xpos, y + ypos, color);
    SetPixel(x - xpos, y + ypos, color);
    SetPixel(x + xpos, y - ypos, color);
    SetPixel(x - xpos, y - ypos, color);
    if (d < 0)
    {
      d = d + b2 * ((xpos << 1) + 3);
      xpos += 1;
    }
    else
    {
      d = d + b2 * ((xpos << 1) + 3) + a2 * (-(ypos << 1) + 2);
      xpos += 1, ypos -= 1;
    }
  }
  d = b2 * (xpos + 0.5) * (xpos + 0.5) + a2 * (ypos - 1) * (ypos - 1) - a2 * b2;
  while (ypos > 0)
  {
    SetPixel(x + xpos, y + ypos, color);
    SetPixel(x - xpos, y + ypos, color);
    SetPixel(x + xpos, y - ypos, color);
    SetPixel(x - xpos, y - ypos, color);
    if (d < 0)
    {
      d = d + b2 * ((xpos << 1) + 2) + a2 * (-(ypos << 1) + 3);
      xpos += 1, ypos -= 1;
    }
    else
    {
      d = d + a2 * (-(ypos << 1) + 3);
      ypos -= 1;
    }
  }
}


void OLED::DrawImage(uint8_t x, uint8_t y, const Image *img, OLED_ColorMode color)
{
  SetBlock(x, y, img->data, img->w, img->h, color);
}

// ================================ 文字绘制 ================================


void OLED::PrintASCIIChar(uint8_t x, uint8_t y, char ch, const ASCIIFont *font, OLED_ColorMode color)
{
  SetBlock(x, y, font->chars + (ch - ' ') * (((font->h + 7) / 8) * font->w), font->w, font->h, color);
}


void OLED::PrintASCIIString(uint8_t x, uint8_t y, const char *str, const ASCIIFont *font, OLED_ColorMode color)
{
  uint8_t x0 = x;
  while (*str)
  {
    PrintASCIIChar(x0, y, *str, font, color);
    x0 += font->w;
    str++;
  }
}

void OLED::PrintASCIIArray(uint8_t x, uint8_t y, const char *arr, uint8_t length, const ASCIIFont *font, OLED_ColorMode color)
{
  uint8_t x0 = x;
  char* it = (char*)arr;
  for(uint8_t i = 0; i < length; i++)
  {
    PrintASCIIChar(x0, y, *(it + i), font, color);
    x0 += font->w;
  }
}

uint8_t OLED::getUTF8Len(char *string)
{
  if ((string[0] & 0x80) == 0x00)
  {
    return 1;
  }
  else if ((string[0] & 0xE0) == 0xC0)
  {
    return 2;
  }
  else if ((string[0] & 0xF0) == 0xE0)
  {
    return 3;
  }
  else if ((string[0] & 0xF8) == 0xF0)
  {
    return 4;
  }
  return 0;
}



void OLED::PrintString(uint8_t x, uint8_t y, char *str, const Font *font, OLED_ColorMode color)
{
  uint16_t i = 0;                                       // 字符串索引
  uint8_t oneLen = (((font->h + 7) / 8) * font->w) + 4; // 一个字模占多少字节
  uint8_t found;                                        // 是否找到字模
  uint8_t utf8Len;                                      // UTF-8编码长度
  uint8_t *head;                                        // 字模头指针
  while (str[i])
  {
    found = 0;
    utf8Len = getUTF8Len(str + i);
    if (utf8Len == 0)
      break; // 有问题的UTF-8编码

    // 寻找字符  TODO 优化查找算法, 二分查找或者hash
    for (uint8_t j = 0; j < font->len; j++)
    {
      head = (uint8_t *)(font->chars) + (j * oneLen);
      if (memcmp(str + i, head, utf8Len) == 0)
      {
        SetBlock(x, y, head + 4, font->w, font->h, color);
        // 移动光标
        x += font->w;
        i += utf8Len;
        found = 1;
        break;
      }
    }

    // 若未找到字模,且为ASCII字符, 则缺省显示ASCII字符
    if (found == 0)
    {
      if (utf8Len == 1)
      {
        PrintASCIIChar(x, y, str[i], font->ascii, color);
        // 移动光标
        x += font->ascii->w;
        i += utf8Len;
      }
      else
      {
        PrintASCIIChar(x, y, ' ', font->ascii, color);
        x += font->ascii->w;
        i += utf8Len;
      }
    }
  }
}
